// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

//! Shader is a script for graphics card, it defines how to draw an object. It also defines a set
//! of external resources needed for the rendering.
//!
//! # Structure
//!
//! Shader has rigid structure that could be described in this code snipped:
//!
//! ```ron
//! (
//!     name: "MyShader",
//!
//!     // A set of resources, the maximum amount of resources is limited by your GPU. The engine
//!     // guarantees, that there could at least 16 textures and 16 resource groups per shader.
//!     resources: [
//!         (
//!             // Each resource binding must have a name.
//!             name: "diffuseTexture",
//!
//!             // Value has limited set of possible variants.
//!             kind: Texture(kind: Sampler2D, fallback: White),
//!
//!             binding: 0
//!         ),
//!         (
//!             name: "properties",
//!             kind: PropertyGroup([
//!                 (
//!                     name: "diffuseColor",
//!                     kind: Color(r: 255, g: 255, b: 255, a: 255),
//!                 ),
//!             ]),
//!             binding: 0
//!         ),
//!         // There are number of built-in property groups which provides useful data for each shader.
//!         // See the full list of built-in property groups below.
//!         (
//!             name: "fyrox_instanceData",
//!             kind: PropertyGroup([
//!                 // Autogenerated
//!             ]),
//!             binding: 1
//!         ),
//!     ],
//!
//!     // A set of passes that are intentionally missing in the shader.
//!     disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"],
//!
//!     // A set of render passes (see a section `Render pass` for more info)
//!     passes: [
//!         (
//!             // Name must match with the name of either standard render pass (see below) or
//!             // one of your passes.
//!             name: "Forward",
//!
//!             // A set of parameters that regulate renderer pipeline state.
//!             // This is mandatory field of each render pass.
//!             draw_parameters: DrawParameters(
//!                 // A face to cull. Either Front or Back.
//!                 cull_face: Some(Back),
//!
//!                 // Color mask. Defines which colors should be written to render target.
//!                 color_write: ColorMask(
//!                     red: true,
//!                     green: true,
//!                     blue: true,
//!                     alpha: true,
//!                 ),
//!
//!                 // Whether to modify depth buffer or not.
//!                 depth_write: true,
//!
//!                 // Whether to use stencil test or not.
//!                 stencil_test: None,
//!
//!                 // Whether to perform depth test when drawing.
//!                 depth_test: Some(Less),
//!
//!                 // Blending options.
//!                 blend: Some(BlendParameters(
//!                     func: BlendFunc(
//!                         sfactor: SrcAlpha,
//!                         dfactor: OneMinusSrcAlpha,
//!                         alpha_sfactor: SrcAlpha,
//!                         alpha_dfactor: OneMinusSrcAlpha,
//!                     ),
//!                     equation: BlendEquation(
//!                         rgb: Add,
//!                         alpha: Add
//!                     )
//!                 )),
//!
//!                 // Stencil options.
//!                 stencil_op: StencilOp(
//!                     fail: Keep,
//!                     zfail: Keep,
//!                     zpass: Keep,
//!                     write_mask: 0xFFFF_FFFF,
//!                 ),
//!
//!                 // Scissor box. Could be something like this:
//!                 //
//!                 // scissor_box: Some(ScissorBox(
//!                 //    x: 10,
//!                 //    y: 20,
//!                 //    width: 100,
//!                 //    height: 30
//!                 // ))
//!                 scissor_box: None
//!             ),
//!
//!             // Vertex shader code.
//!             vertex_shader:
//!                 r#"
//!                 layout(location = 0) in vec3 vertexPosition;
//!                 layout(location = 1) in vec2 vertexTexCoord;
//!
//!                 out vec2 texCoord;
//!
//!                 void main()
//!                 {
//!                     texCoord = vertexTexCoord;
//!                     gl_Position = fyrox_instanceData.worldViewProjection * vec4(vertexPosition, 1.0);
//!                 }
//!                 "#,
//!
//!             // Fragment shader code.
//!             fragment_shader:
//!                 r#"
//!                 out vec4 FragColor;
//!
//!                 in vec2 texCoord;
//!
//!                 void main()
//!                 {
//!                     FragColor = properties.diffuseColor * texture(diffuseTexture, texCoord);
//!                 }
//!                 "#,
//!         )
//!     ],
//! )
//! ```
//!
//! Shader should contain at least one render pass to actually do some job. A shader could not
//! have properties at all. Currently only vertex and fragment programs are supported. Each
//! program mush be written in GLSL. Comprehensive GLSL documentation can be found
//! [here](https://www.khronos.org/opengl/wiki/Core_Language_(GLSL))
//!
//! # Render pass
//!
//! Modern rendering is a very complex thing that requires drawing an object multiple times
//! with different "scripts". For example to draw an object with shadows you need to draw an
//! object twice: one directly in a render target, and one in a shadow map. Such stages called
//! render passes.
//!
//! Binding of shaders to render passes is done via names, each render pass has unique name.
//!
//! ## Predefined passes
//!
//! There are number of predefined render passes:
//!
//! - `GBuffer` - A pass that fills a set of textures (render targets) with various data about each
//!   rendered object (depth, normal, albedo, etc.). These textures then are used for physically-based
//!   lighting. Use this pass when you want the standard lighting to work with your objects.
//!
//! - `Forward` - A pass that draws an object directly in a render target. It could be used to render
//!   translucent objects.
//!
//! - `SpotShadow` - A pass that emits depth values for an object, later this depth map will be
//!   used to render shadows.
//!
//! - `PointShadow` - A pass that emits distance from a fragment to a point light, later this depth
//!   map will be used to render shadows.
//!
//! - `DirectionalShadow` - A pass that emits depth values for an object, later this depth map will be
//!   used to render shadows for directional cascaded shadows.
//!
//! ## Disables passes
//!
//! A render pass can be marked as "disabled" to prevent the renderer emitting errors when it tries
//! to use such render pass. This mechanism of explicit marking the pass disabled is here to ensure
//! correctness of shaders. To explicitly disable a pass, add its name to `disabled_passes` array:
//!
//! ```ron
//! disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"],
//! ```
//!
//! # Resources
//!
//! Each shader requires a specific set of external resources that will be used during the rendering.
//! This set is defined in `resources` section of the shader and could contain the following resources:
//!
//! - `Texture` - a texture of arbitrary type
//! - `PropertyGroup` - a group of numeric properties.
//!
//! ## Binding points
//!
//! Shader resource must define a unique (over its type) binding index. The engine will use these
//! points to prepare appropriate resource descriptor sets for GPU. Keep in mind, that binding point
//! indices are **unique** per each type of resource. This means that a set of texture resource could
//! use the same indices as property groups. The binding points must be unique in its group. If
//! there are more than one resource of a certain type, that shares the same binding point, the
//! engine will refuse to use such shader.
//!
//! ## Built-in resources
//!
//! There are number of built-in resources, that Fyrox will try to assign automatically if they're
//! defined in your shader, something like this:
//!
//! ```ron
//! (
//!     name: "fyrox_instanceData",
//!     kind: PropertyGroup([
//!         // Autogenerated
//!     ]),
//!     binding: 1
//! ),
//! ```
//!
//! The full list of built-in resources is defined below.
//!
//! ### `fyrox_instanceData`
//!
//! Property group. Provided for each rendered surface instance.
//!
//! | Name                 | Type       | Description                                 |
//! |----------------------|------------|---------------------------------------------|
//! | worldMatrix          | `mat4`     | Local-to-world transformation.              |
//! | worldViewProjection  | `mat4`     | Local-to-clip-space transform.              |
//! | blendShapesCount     | `int`      | Total amount of blend shapes.               |
//! | useSkeletalAnimation | `bool`     | Whether skinned meshes is rendering or not. |
//! | blendShapesWeights   | `vec4[32]` | Blend shape weights.                        |
//!
//! ### `fyrox_boneMatrices`
//!
//! Property group. Provided for each rendered surface, that has skeletal animation.
//!
//! | Name     | Type        | Description   |
//! |----------|-------------|---------------|
//! | matrices | `mat4[256]` | Bone matrices |
//!
//!
//! ### `fyrox_cameraData`
//!
//! Property group. Contains camera properties. It contains info not only about scene camera,
//! but also observer info when rendering shadow maps. In other words - it is generic observer
//! properties.
//!
//! | Name                 | Type       | Description                                      |
//! |----------------------|------------|--------------------------------------------------|
//! | viewProjectionMatrix | `mat4`     | World-to-clip-space transformation.              |
//! | position             | `vec3`     | World-space position of the camera.              |
//! | upVector             | `vec3`     | World-space up-vector of the camera.             |
//! | sideVector           | `vec3`     | World-space side-vector of the camera.           |
//! | zNear                | `float`    | Near clipping plane location.                    |
//! | zFar                 | `float`    | Far clipping plane location.                     |
//! | zRange               | `float`    | `zFar - zNear`                                   |
//!
//! ### `fyrox_lightData`
//!
//! Property group. Available only in shadow passes.
//!
//! | Name              | Type   | Description                                                |
//! |-------------------|--------|------------------------------------------------------------|
//! | lightPosition     | `vec3` | World-space light source position. Only for shadow passes. |
//! | ambientLightColor | `vec4` | Ambient lighting color of the scene.                       |
//!
//! ### `fyrox_lightsBlock`
//!
//! Property group. Information about visible light sources
//!
//! | Name              | Type       | Description                                             |
//! |------------------ |------------|---------------------------------------------------------|
//! | lightCount        | `int`      | Total amount of light sources visible on screen.        |
//! | lightsColorRadius | `vec4[16]` | Color (xyz) and radius (w) of light source              |
//! | lightsParameters  | `vec2[16]` | Hot-spot cone angle cos (x) and half cone angle cos (y) |
//! | lightsPosition    | `vec3[16]` | World-space light position.                             |
//! | lightsDirection   | `vec3[16]` | World-space light direction                             |
//!
//! ### `fyrox_graphicsSettings`
//!
//! Property group. Contains graphics options of the renderer.
//!
//! | Name   | Type       | Description                                       |
//! |--------|------------|---------------------------------------------------|
//! | usePom | `bool`     | Whether to use parallax occlusion mapping or not. |
//!
//! ### `fyrox_sceneDepth`
//!
//! Texture. Contains depth values of scene. Available **only** after opaque geometry is
//! rendered (read - G-Buffer is filled). Typical usage is something like this:
//!
//! ```ron
//! (
//!     name: "fyrox_sceneDepth",
//!     kind: Texture(kind: Sampler2D, fallback: White),
//!     binding: 1
//! ),
//! ```
//!
//! ### `fyrox_widgetData`
//!
//! Property group. Contains UI widget-specific data, that can be used in UI shaders.
//!
//! | Name                | Type        | Description                                                |
//! |---------------------|-------------|------------------------------------------------------------|
//! | worldViewProjection | `mat4`      | World-to-clip-space transformation matrix.                 |
//! | solidColor          | `vec4`      | RGBA color.                                                |
//! | gradientColors      | `vec4[16]`  | A set of gradient colors.                                  |
//! | gradientStops       | `float[16]` | A set of normalized (0.0-1.0) stops along the axis.        |
//! | gradientOrigin      | `vec2`      | Origin position of the gradient (local coordinates).       |
//! | gradientEnd         | `vec2`      | End position of the gradient (local coordinates).          |
//! | gradientPointCount  | `int`       | Total number of gradient points.                           |
//! | resolution          | `vec2`      | Frame buffer size to which the widget is drawn to.         |
//! | boundsMin           | `vec2`      | Top-left point of the screen space bounding rectangle.     |
//! | boundsMax           | `vec2`      | Right-bottom point of the screen space bounding rectangle. |
//! | isFont              | `bool`      | `true` if the widget is a text, `false` - otherwise.       |
//! | opacity             | `float`     | Opacity (0.0-1.0 range).                                   |
//! | brushType           | `int`       | Brush type (0 solid, 1-linear gradient, 2-radial gradient  |
//!
//! # Code generation
//!
//! Fyrox automatically generates code for resource bindings. This is made specifically to prevent
//! subtle mistakes. For example when you define this set of resources:
//!
//! ```ron
//! (
//!     name: "MyShader",
//!
//!     resources: [
//!         (
//!             name: "diffuseTexture",
//!             kind: Texture(kind: Sampler2D, fallback: White),
//!             binding: 0
//!         ),
//!         (
//!             name: "normalTexture",
//!             kind: Texture(kind: Sampler2D, fallback: Normal),
//!             binding: 1
//!         ),
//!         (
//!             name: "properties",
//!             kind: PropertyGroup([
//!                 (
//!                     name: "texCoordScale",
//!                     kind: Vector2(value: (1.0, 1.0)),
//!                 ),
//!                 (
//!                     name: "diffuseColor",
//!                     kind: Color(r: 255, g: 255, b: 255, a: 255),
//!                 ),
//!             ]),
//!             binding: 0
//!         ),
//!     ]
//! )
//! ```
//!
//! The engine generates the following code and adds it to source code of every shader of every pass
//! automatically:
//!
//! ```glsl
//! uniform sampler2D diffuseTexture;
//! uniform sampler2D normalTexture;
//! struct Tproperties {
//!     vec2 texCoordScale;
//!     vec4 diffuseColor;
//! };
//! layout(std140) uniform Uproperties { Tproperties properties; }
//! ```
//!
//! The most important thing is that the engine keeps properties in the `struct Tproperties` in
//! correct order and forces `std140` layout on the generated uniform block. Since the engine knows
//! the layout of the properties from their definition section, it could easily form a memory block
//! with all required alignments and paddings that could be uploaded to GPU. The next important thing
//! is that the engine batches all the data needed into a large chunks of data and uploads them
//! all at once, which is much faster.
//!
//! # Drawing parameters
//!
//! Drawing parameters defines which GPU functions to use and at which state. For example, to render
//! transparent objects you need to enable blending with specific blending rules. Or you need to disable
//! culling to draw objects from both sides. This is when draw parameters comes in handy.
//!
//! There are relatively large list of drawing parameters, and it could confuse a person who didn't get
//! used to work with graphics. The following list should help you to use drawing parameters correctly.
//!
//! - cull_face
//!     - Defines which side of polygon should be culled.
//!     - **Possible values:** `None`, [Some(CullFace::XXX)](fyrox_graphics::CullFace)
//!
//! - color_write:
//!     - Defines which components of color should be written to a render target
//!     - **Possible values:** [ColorMask](fyrox_graphics::ColorMask)(...)
//!
//!  - depth_write:
//!     - Whether to modify depth buffer or not.
//!     - **Possible values:** `true/false`
//!
//!  - stencil_test:
//!     - Whether to use stencil test or not.
//!     - **Possible values:**
//!         - `None`
//!         - Some([StencilFunc](fyrox_graphics::StencilFunc))
//!
//!  - depth_test:
//!      - Whether to perform depth test when drawing.
//!      - **Possible values:** `true/false`
//!
//!   - blend:
//!      - Blending options.
//!      - **Possible values:**
//!         - `None`
//!         - Some([BlendFunc](fyrox_graphics::BlendFunc))
//!
//!   - stencil_op:
//!      - Stencil options.
//!      - **Possible values:** [StencilOp](fyrox_graphics::StencilOp)
//!
//!    - `scissor_box`:
//!       - A rectangle that is used for clipping (screen-space). This value can be redefined when
//!         issuing a drawing command.
//!       - `None`
//!       - `Some(ScissorBox(x: 10, y: 20, width: 100, height: 30))`
//!
//! # Standard shader
//!
//! By default, Fyrox uses standard material for rendering, it covers 95% of uses cases and it is very
//! flexible. To get standard shader instance, use [`ShaderResource::standard`]
//!
//! ```no_run
//! # use fyrox_material::shader::{ShaderResource, ShaderResourceExtension};
//!
//! let standard_shader = ShaderResource::standard();
//! ```
//!
//! Usually you don't need to get this shader manually, using of [Material::standard](super::Material::standard)
//! is enough.

use fyrox_core::some_or_continue;
use fyrox_core::{
    io::FileError, reflect::prelude::*, sparse::AtomicIndex, uuid::Uuid, visitor::prelude::*,
    TypeUuidProvider,
};
pub use fyrox_graphics::gpu_program::{
    SamplerFallback, ShaderResourceDefinition, ShaderResourceKind,
};
use fyrox_graphics::{gpu_program::ShaderProperty, DrawParameters};
use fyrox_resource::{
    embedded_data_source, io::ResourceIo, manager::BuiltInResource, untyped::ResourceKind,
    Resource, ResourceData,
};
use lazy_static::lazy_static;
use ron::ser::PrettyConfig;
use serde::{Deserialize, Serialize};
use std::{
    error::Error,
    fmt::{Display, Formatter},
    fs::File,
    io::Write,
    path::Path,
    sync::Arc,
};
use uuid::uuid;

pub mod loader;

/// A name of the standard shader.
pub const STANDARD_SHADER_NAME: &str = "Standard";

/// A name of the standard 2D shader.
pub const STANDARD_2D_SHADER_NAME: &str = "Standard2D";

/// A name of the standard particle system shader.
pub const STANDARD_PARTICLE_SYSTEM_SHADER_NAME: &str = "StandardParticleSystem";

/// A name of the standard two-sides shader.
pub const STANDARD_TWOSIDES_SHADER_NAME: &str = "StandardTwoSides";

/// A name of the standard terrain shader.
pub const STANDARD_TERRAIN_SHADER_NAME: &str = "StandardTerrain";

/// A name of the standard tile shader.
pub const STANDARD_TILE_SHADER_NAME: &str = "StandardTile";

/// A name of the standard sprite shader.
pub const STANDARD_SPRITE_SHADER_NAME: &str = "StandardSprite";

/// A name of the standard widget shader.
pub const STANDARD_WIDGET_SHADER_NAME: &str = "StandardWidget";

/// Internal state of the shader.
///
/// # Notes
///
/// Usually you don't need to access internals of the shader, but there sometimes could be a need to
/// read shader definition, to get supported passes and properties.
#[derive(Default, Debug, Clone, Reflect, Visit)]
pub struct Shader {
    /// Shader definition contains description of properties and render passes.
    #[visit(optional)]
    pub definition: ShaderDefinition,

    /// An id that can be used to create associated GPU resources.
    #[reflect(hidden)]
    #[visit(skip)]
    pub cache_index: Arc<AtomicIndex>,
}

impl TypeUuidProvider for Shader {
    fn type_uuid() -> Uuid {
        uuid!("f1346417-b726-492a-b80f-c02096c6c019")
    }
}

/// A render pass definition. See [`ShaderResource`] docs for more info about render passes.
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Reflect, Visit)]
pub struct RenderPassDefinition {
    /// A name of render pass.
    pub name: String,
    /// A set of parameters that will be used in a render pass.
    #[serde(default)]
    pub draw_parameters: DrawParameters,
    /// A source code of vertex shader.
    pub vertex_shader: String,
    /// Vertex shader line number.
    #[serde(default)]
    pub vertex_shader_line: isize,
    /// A source code of fragment shader.
    pub fragment_shader: String,
    /// Fragment shader line number.
    #[serde(default)]
    pub fragment_shader_line: isize,
}

/// A definition of the shader.
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Reflect, Visit)]
pub struct ShaderDefinition {
    /// A name of the shader.
    pub name: String,
    /// A set of render passes.
    pub passes: Vec<RenderPassDefinition>,
    /// A set of resource definitions.
    pub resources: Vec<ShaderResourceDefinition>,
    /// A list of names of disabled render passes. It is used to strictly indicate that certain
    /// passes are intentionally disabled in the rendering process.
    #[serde(default)]
    pub disabled_passes: Vec<String>,
}

impl ShaderDefinition {
    /// Maximum number of simultaneous light sources that can be passed into a standard lights data
    /// block.
    pub const MAX_LIGHTS: usize = 16;

    /// Maximum number of bone matrices per shader.
    pub const MAX_BONE_MATRICES: usize = 255;

    /// Maximum number of blend shape weight groups (packed weights of blend shapes into vec4).
    pub const MAX_BLEND_SHAPE_WEIGHT_GROUPS: usize = 32;

    /// Maximum number of gradient values per widget.
    pub const MAX_GRADIENT_VALUE_COUNT: usize = 16;

    fn find_shader_line_locations(&mut self, str: &str) {
        let mut line_ends = Vec::new();
        for (i, ch) in str.bytes().enumerate() {
            if ch == b'\n' {
                line_ends.push(i);
            }
        }
        if str.bytes().last().is_some_and(|ch| ch != b'\n') {
            line_ends.push(str.len());
        }

        fn find_line(line_ends: &[usize], byte_pos: usize) -> isize {
            line_ends
                .windows(2)
                .enumerate()
                .find_map(|(line_num, ends)| {
                    if (ends[0]..ends[1]).contains(&byte_pos) {
                        Some(line_num)
                    } else {
                        None
                    }
                })
                .unwrap_or(0) as isize
                + 1
        }

        let vertex_shader_regex = regex::Regex::new(r#"vertex_shader\s*:\s*r?#*""#).unwrap();
        let fragment_shader_regex = regex::Regex::new(r#"fragment_shader\s*:\s*r?#*""#).unwrap();

        let mut substr = str;
        for pass in self.passes.iter_mut() {
            let name_location = some_or_continue!(substr.find(&format!("\"{}\"", pass.name)));
            let vertex_shader_location = some_or_continue!(vertex_shader_regex.find(substr));
            let fragment_shader_location = some_or_continue!(fragment_shader_regex.find(substr));
            let offset = str.len() - substr.len();
            pass.vertex_shader_line = find_line(&line_ends, offset + vertex_shader_location.end());
            pass.fragment_shader_line =
                find_line(&line_ends, offset + fragment_shader_location.end());
            let max = name_location
                .max(vertex_shader_location.end())
                .max(fragment_shader_location.end());
            substr = &substr[(max + 1)..];
        }
    }

    fn from_str(str: &str) -> Result<Self, ShaderError> {
        let mut definition: ShaderDefinition = ron::de::from_str(str)?;
        definition.generate_built_in_resources();
        definition.find_shader_line_locations(str);
        Ok(definition)
    }

    fn generate_built_in_resources(&mut self) {
        for resource in self.resources.iter_mut() {
            let ShaderResourceKind::PropertyGroup(ref mut properties) = resource.kind else {
                continue;
            };

            match resource.name.as_str() {
                "fyrox_widgetData" => {
                    properties.clear();
                    properties.extend([
                        ShaderProperty::new_matrix4("worldViewProjection"),
                        ShaderProperty::new_color("solidColor"),
                        ShaderProperty::new_vec4_f32_array(
                            "gradientColors",
                            Self::MAX_GRADIENT_VALUE_COUNT,
                        ),
                        ShaderProperty::new_f32_array(
                            "gradientStops",
                            Self::MAX_GRADIENT_VALUE_COUNT,
                        ),
                        ShaderProperty::new_vector2("gradientOrigin"),
                        ShaderProperty::new_vector2("gradientEnd"),
                        ShaderProperty::new_vector2("resolution"),
                        ShaderProperty::new_vector2("boundsMin"),
                        ShaderProperty::new_vector2("boundsMax"),
                        ShaderProperty::new_bool("isFont"),
                        ShaderProperty::new_float("opacity"),
                        ShaderProperty::new_int("brushType"),
                        ShaderProperty::new_int("gradientPointCount"),
                    ]);
                }
                "fyrox_cameraData" => {
                    properties.clear();
                    properties.extend([
                        ShaderProperty::new_matrix4("viewProjectionMatrix"),
                        ShaderProperty::new_vector3("position"),
                        ShaderProperty::new_vector3("upVector"),
                        ShaderProperty::new_vector3("sideVector"),
                        ShaderProperty::new_float("zNear"),
                        ShaderProperty::new_float("zFar"),
                        ShaderProperty::new_float("zRange"),
                    ]);
                }
                "fyrox_lightData" => {
                    properties.clear();
                    properties.extend([
                        ShaderProperty::new_vector3("lightPosition"),
                        ShaderProperty::new_vector4("ambientLightColor"),
                    ]);
                }
                "fyrox_graphicsSettings" => {
                    properties.clear();
                    properties.extend([ShaderProperty::new_bool("usePOM")]);
                }
                "fyrox_lightsBlock" => {
                    properties.clear();
                    properties.extend([
                        ShaderProperty::new_int("lightCount"),
                        ShaderProperty::new_vec4_f32_array("lightsColorRadius", Self::MAX_LIGHTS),
                        ShaderProperty::new_vec2_f32_array("lightsParameters", Self::MAX_LIGHTS),
                        ShaderProperty::new_vec3_f32_array("lightsPosition", Self::MAX_LIGHTS),
                        ShaderProperty::new_vec3_f32_array("lightsDirection", Self::MAX_LIGHTS),
                    ])
                }
                "fyrox_instanceData" => {
                    properties.clear();
                    properties.extend([
                        ShaderProperty::new_matrix4("worldMatrix"),
                        ShaderProperty::new_matrix4("worldViewProjection"),
                        ShaderProperty::new_int("blendShapesCount"),
                        ShaderProperty::new_bool("useSkeletalAnimation"),
                        ShaderProperty::new_vec4_f32_array(
                            "blendShapesWeights",
                            Self::MAX_BLEND_SHAPE_WEIGHT_GROUPS,
                        ),
                    ]);
                }
                "fyrox_boneMatrices" => {
                    properties.clear();
                    properties.extend([ShaderProperty::new_mat4_f32_array(
                        "matrices",
                        Self::MAX_BONE_MATRICES,
                    )])
                }
                _ => (),
            }
        }
    }
}

impl Shader {
    /// Creates a shader from file.
    pub async fn from_file<P: AsRef<Path>>(
        path: P,
        io: &dyn ResourceIo,
    ) -> Result<Self, ShaderError> {
        let bytes = io.load_file(path.as_ref()).await?;
        let content = String::from_utf8_lossy(&bytes);
        Ok(Self {
            definition: ShaderDefinition::from_str(&content)?,
            cache_index: Default::default(),
        })
    }

    /// Creates a shader from string.
    pub fn from_string(str: &str) -> Result<Self, ShaderError> {
        Ok(Self {
            definition: ShaderDefinition::from_str(str)?,
            cache_index: Default::default(),
        })
    }

    /// Creates a shader from string represented as raw bytes. This function will fail if the `bytes`
    /// does not contain Utf8-encoded string.
    pub fn from_string_bytes(bytes: &[u8]) -> Result<Self, ShaderError> {
        Ok(Self {
            definition: ShaderDefinition::from_str(
                std::str::from_utf8(bytes).map_err(|_| ShaderError::NotUtf8Source)?,
            )?,
            cache_index: Default::default(),
        })
    }
}

impl ResourceData for Shader {
    fn type_uuid(&self) -> Uuid {
        <Self as TypeUuidProvider>::type_uuid()
    }

    fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
        let mut file = File::create(path)?;
        file.write_all(
            ron::ser::to_string_pretty(&self.definition, PrettyConfig::default())?.as_bytes(),
        )?;
        Ok(())
    }

    fn can_be_saved(&self) -> bool {
        true
    }

    fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
        Some(Box::new(self.clone()))
    }
}

/// A set of possible error variants that can occur during shader loading.
#[derive(Debug)]
pub enum ShaderError {
    /// An i/o error has occurred.
    Io(FileError),

    /// A parsing error has occurred.
    ParseError(ron::error::SpannedError),

    /// Bytes does not represent Utf8-encoded string.
    NotUtf8Source,
}

impl Display for ShaderError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ShaderError::Io(v) => {
                write!(f, "A file load error has occurred {v:?}")
            }
            ShaderError::ParseError(v) => {
                write!(f, "A parsing error has occurred {v:?}")
            }
            ShaderError::NotUtf8Source => {
                write!(f, "Bytes does not represent Utf8-encoded string.")
            }
        }
    }
}

impl From<ron::error::SpannedError> for ShaderError {
    fn from(e: ron::error::SpannedError) -> Self {
        Self::ParseError(e)
    }
}

impl From<FileError> for ShaderError {
    fn from(e: FileError) -> Self {
        Self::Io(e)
    }
}

/// Type alias for shader resources.
pub type ShaderResource = Resource<Shader>;

/// Extension trait for shader resources.
pub trait ShaderResourceExtension: Sized {
    /// Creates new shader from given string. Input string must have the format defined in
    /// examples for [`ShaderResource`].
    fn from_str(id: Uuid, str: &str, kind: ResourceKind) -> Result<Self, ShaderError>;

    /// Returns an instance of standard shader.
    fn standard() -> Self;

    /// Returns an instance of standard 2D shader.
    fn standard_2d() -> Self;

    /// Returns an instance of standard particle system shader.
    fn standard_particle_system() -> Self;

    /// Returns an instance of standard sprite shader.
    fn standard_sprite() -> Self;

    /// Returns an instance of standard terrain shader.
    fn standard_terrain() -> Self;

    /// Returns an instance of standard tile shader.
    fn standard_tile() -> Self;

    /// Returns an instance of standard two-sides terrain shader.
    fn standard_twosides() -> Self;

    /// Returns an instance of standard widget shader.
    fn standard_widget() -> Self;

    /// Returns a list of standard shader.
    fn standard_shaders() -> [&'static BuiltInResource<Shader>; 8];
}

impl ShaderResourceExtension for ShaderResource {
    fn from_str(id: Uuid, str: &str, kind: ResourceKind) -> Result<Self, ShaderError> {
        Ok(Resource::new_ok(id, kind, Shader::from_string(str)?))
    }

    fn standard() -> Self {
        STANDARD.resource()
    }

    fn standard_2d() -> Self {
        STANDARD_2D.resource()
    }

    fn standard_particle_system() -> Self {
        STANDARD_PARTICLE_SYSTEM.resource()
    }

    fn standard_sprite() -> Self {
        STANDARD_SPRITE.resource()
    }

    fn standard_terrain() -> Self {
        STANDARD_TERRAIN.resource()
    }

    fn standard_tile() -> Self {
        STANDARD_TILE.resource()
    }

    fn standard_twosides() -> Self {
        STANDARD_TWOSIDES.resource()
    }

    fn standard_widget() -> Self {
        STANDARD_WIDGET.resource()
    }

    fn standard_shaders() -> [&'static BuiltInResource<Shader>; 8] {
        [
            &STANDARD,
            &STANDARD_2D,
            &STANDARD_PARTICLE_SYSTEM,
            &STANDARD_SPRITE,
            &STANDARD_TERRAIN,
            &STANDARD_TWOSIDES,
            &STANDARD_TILE,
            &STANDARD_WIDGET,
        ]
    }
}

lazy_static! {
    /// Standard shader.
    pub static ref STANDARD: BuiltInResource<Shader> = BuiltInResource::new(
        STANDARD_SHADER_NAME,
        embedded_data_source!("standard/standard.shader"),
        |data| {
            ShaderResource::new_ok(
                uuid!("87195f6e-cba4-4c27-9f89-d0bf726db965"),
                ResourceKind::External,
                Shader::from_string_bytes(data).unwrap(),
            )
        }
    );
    /// Standard 2D shader.
    pub static ref STANDARD_2D: BuiltInResource<Shader> = BuiltInResource::new(
        STANDARD_2D_SHADER_NAME,
        embedded_data_source!("standard/standard2d.shader"),
        |data| ShaderResource::new_ok(
            uuid!("55fa05b0-3c25-4e46-bae7-65f093185b75"),
            ResourceKind::External,
            Shader::from_string_bytes(data).unwrap(),
        )
    );
    /// Standard particle system shader.
    pub static ref STANDARD_PARTICLE_SYSTEM: BuiltInResource<Shader> = BuiltInResource::new(
        STANDARD_PARTICLE_SYSTEM_SHADER_NAME,
        embedded_data_source!("standard/standard_particle_system.shader"),
        |data| ShaderResource::new_ok(
            uuid!("eb474445-6a25-4481-bca9-f919699c300f"),
            ResourceKind::External,
            Shader::from_string_bytes(data).unwrap(),
        )
    );
    /// Standard sprite shader.
    pub static ref STANDARD_SPRITE: BuiltInResource<Shader> = BuiltInResource::new(
        STANDARD_SPRITE_SHADER_NAME,
        embedded_data_source!("standard/standard_sprite.shader"),
        |data| ShaderResource::new_ok(
            uuid!("a135826a-4c1b-46d5-ba1f-0c9a226aa52c"),
            ResourceKind::External,
            Shader::from_string_bytes(data).unwrap(),
        )
    );
    /// Standard terrain shader.
    pub static ref STANDARD_TERRAIN: BuiltInResource<Shader> = BuiltInResource::new(
        STANDARD_TERRAIN_SHADER_NAME,
        embedded_data_source!("standard/terrain.shader"),
        |data| {
            ShaderResource::new_ok(
                uuid!("4911aafe-9bb1-4115-a958-25b57b87b51e"),
                ResourceKind::External,
                Shader::from_string_bytes(data).unwrap(),
            )
        }
    );
    /// Standard tile shader.
    pub static ref STANDARD_TILE: BuiltInResource<Shader> = BuiltInResource::new(
        STANDARD_TILE_SHADER_NAME,
        embedded_data_source!("standard/tile.shader"),
        |data| {
            ShaderResource::new_ok(
                uuid!("5f29dd3a-ea99-480c-bb02-d2c6420843b1"),
                ResourceKind::External,
                Shader::from_string_bytes(data).unwrap(),
            )
        }
    );
    /// Standard two-sides shader.
    pub static ref STANDARD_TWOSIDES: BuiltInResource<Shader> = BuiltInResource::new(
        STANDARD_TWOSIDES_SHADER_NAME,
        embedded_data_source!("standard/standard-two-sides.shader"),
        |data| ShaderResource::new_ok(
            uuid!("f7979409-5185-4e1c-a644-d53cea64af8f"),
            ResourceKind::External,
            Shader::from_string_bytes(data).unwrap(),
        )
    );
    /// Standard widget shader.
    pub static ref STANDARD_WIDGET: BuiltInResource<Shader> = BuiltInResource::new(
        STANDARD_WIDGET_SHADER_NAME,
        embedded_data_source!("standard/widget.shader"),
        |data| ShaderResource::new_ok(
            uuid!("f5908aa4-e187-42a8-95d2-dc6577f6def4"),
            ResourceKind::External,
            Shader::from_string_bytes(data).unwrap(),
        )
    );
}

#[cfg(test)]
mod test {
    use crate::shader::{
        RenderPassDefinition, SamplerFallback, ShaderDefinition, ShaderResource,
        ShaderResourceDefinition, ShaderResourceExtension, ShaderResourceKind,
    };
    use fyrox_graphics::gpu_program::SamplerKind;
    use fyrox_resource::untyped::ResourceKind;
    use uuid::Uuid;

    #[test]
    fn test_shader_load() {
        let code = r#"
            (
                name: "TestShader",

                resources: [
                    (
                        name: "diffuseTexture",
                        kind: Texture(kind: Sampler2D, fallback: White),
                        binding: 0
                    ),
                ],

                passes: [
                    (
                        name: "GBuffer",
                        draw_parameters: DrawParameters(
                            cull_face: Some(Back),
                            color_write: ColorMask(
                                red: true,
                                green: true,
                                blue: true,
                                alpha: true,
                            ),
                            depth_write: true,
                            stencil_test: None,
                            depth_test: Some(Less),
                            blend: None,
                            stencil_op: StencilOp(
                                fail: Keep,
                                zfail: Keep,
                                zpass: Keep,
                                write_mask: 0xFFFF_FFFF,
                            ),
                            scissor_box: None
                        ),
                        vertex_shader: "<CODE>",
                        fragment_shader: "<CODE>",
                    ),
                ],
            )
            "#;

        let shader =
            ShaderResource::from_str(Uuid::new_v4(), code, ResourceKind::External).unwrap();
        let data = shader.data_ref();

        let reference_definition = ShaderDefinition {
            name: "TestShader".to_owned(),
            resources: vec![ShaderResourceDefinition {
                name: "diffuseTexture".into(),
                kind: ShaderResourceKind::Texture {
                    kind: SamplerKind::Sampler2D,
                    fallback: SamplerFallback::White,
                },
                binding: 0,
            }],
            passes: vec![RenderPassDefinition {
                name: "GBuffer".to_string(),
                draw_parameters: Default::default(),
                vertex_shader: "<CODE>".to_string(),
                vertex_shader_line: 35,
                fragment_shader: "<CODE>".to_string(),
                fragment_shader_line: 36,
            }],
            disabled_passes: vec![],
        };

        assert_eq!(data.definition, reference_definition);
    }
}
